//
// This file is part of cpp-lib, a library of C++ functions and classes.
// Cpp-lib is copyright (C) 2004 and onwards, The CPL Interest Group.
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library (see the file COPYING); if not, write to the
// Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
// 02111-1307  USA
//
// $Id$
//


#ifndef CPP_LIB_NETWORK_H
#define CPP_LIB_NETWORK_H

#include <vector>
#include <istream>
#include <ostream>
#include <streambuf>
#include <stdexcept>
#include <exception>
#include <limits>

#include <boost/noncopyable.hpp>
#include <boost/lexical_cast.hpp>

#include "cpp-lib/util.h"


#include "cpp-lib/platform/wrappers.h"


namespace cpl {

namespace util {

namespace network {

class acceptor   ;
class connection ;

} // namespace cpl

} // namespace util


namespace detail_ {


void throw_socket_error( std::string const& msg ) ;


struct socket_resource_traits {

  static socketfd_t invalid() ;
  static bool valid  ( socketfd_t const s ) ;
  static void dispose( socketfd_t const s ) ;
  
} ;

template<>
struct streambuf_traits< cpl::util::network::connection > {

  typedef cpl::util::network::connection resource_type ;

  static long read 
  ( resource_type& , char      * buf , long n ) ;
  static long write
  ( resource_type& , char const* buf , long n ) ;

  static void shutdown_read ( resource_type& ) ;
  static void shutdown_write( resource_type& ) ;

} ;


typedef 
cpl::util::auto_resource< socketfd_t , socket_resource_traits > 
auto_socket ;


struct socketfd_proxy {

  socketfd_proxy( socketfd_t const& s ) : s( s ) {}

  socketfd_t const& s ;

} ;


inline void check_socktype( int const type ) 
{ always_assert( SOCK_DGRAM == type || SOCK_STREAM == type ) ; }

inline void check_family( int const family ) 
{ always_assert( AF_INET == family || AF_INET6 == family ) ; }
  

template< int type >
struct socket : private boost::noncopyable {

  socketfd_t const& fd() const { return fd_.get() ; }

  socket( int const , socketfd_t fd ) : fd_( fd ) {}

  socket( int const family ) {
    
    check_socktype( type ) ;
    check_family( family ) ;

    fd_.reset( ::socket( family , type , 0 ) ) ;

    if( !fd_.valid() )
    { throw_socket_error( "socket" ) ; }
    
  }


private:

  auto_socket fd_ ;

} ;


typedef socket< SOCK_DGRAM  > datagram_socket ;
typedef socket< SOCK_STREAM >   stream_socket ;


template< int type >
struct address {

  // Invalid address.
  address() : addrlen( maxlength() ) {}

  address( sockaddr_storage const& addr , socklen_t addrlen )
  : addr( addr ) , addrlen( addrlen ) {}

  std::string const host() const ;
  std::string const port() const ;

  std::string const host_name() const ;
  std::string const port_name() const ;

  std::string const fqdn() const ;

  bool dgram() const { return SOCK_DGRAM == type ; }

  sa_family_t family() const { return sockaddr_pointer()->sa_family ; }
  socklen_t length() const { return addrlen ; }

  socklen_t maxlength() const { return sizeof( sockaddr_storage ) ; }
  void set_maxlength() { addrlen = maxlength() ; }

  // Accessors for the low-level stuff...
  sockaddr* sockaddr_pointer() 
  { return reinterpret_cast< sockaddr* >( &addr ) ; }

  sockaddr const* sockaddr_pointer() const
  { return reinterpret_cast< sockaddr const* >( &addr ) ; }

  socklen_t const* socklen_pointer() const { return &addrlen ; }
  socklen_t      * socklen_pointer()       { return &addrlen ; }

  // From struct addrinfo:
  sockaddr_storage addr    ;
  socklen_t        addrlen ;

} ;


template< int type >
std::ostream& operator<<( std::ostream& os , address< type > const& a ) {

  os << a.host_name() << ":" << a.port_name() ; 

  return os ;

}


template< int type >
std::vector< address< type > >
my_getaddrinfo( char const* const n , char const* const s ) ;


template< int type >
long my_sendto
( socketfd_t const fd , address< type > const& a , char const* p , long n ) {

  long ret ;
  do { ret = ::sendto( fd , p , n , 0 , a.sockaddr_pointer() , a.length() ) ; }
  while( cpl::detail_::EINTR_repeat( ret ) ) ;

  return ret ;

}


template< int type >
std::string const
my_getnameinfo(
  address< type >  const& a ,
  bool             const node    ,  // node or service?
  bool             const numeric ,  // numeric or name?
  bool             const fqdn       // fqdn (for node)?
) ;


} // namespace detail_


namespace util {

namespace network {


////////////////////////////////////////////////////////////////////////
// cpp-lib specific functionality.
////////////////////////////////////////////////////////////////////////

//
// Check n and throw an exception if it is not a valid port number.
//

template< typename T >
void check_port( T const& n ) {
  
  if( n < 0 || n > 65535 )
  { throw std::runtime_error( "TCP/UDP port number out of range" ) ; }

}


////////////////////////////////////////////////////////////////////////
// Proposed standard interface.
////////////////////////////////////////////////////////////////////////

typedef cpl::detail_::address< SOCK_STREAM >   stream_address ;
typedef cpl::detail_::address< SOCK_DGRAM  > datagram_address ;

typedef std::vector<   stream_address >   stream_address_list ;
typedef std::vector< datagram_address > datagram_address_list ;


inline stream_address_list const resolve_stream
( std::string const& n , std::string const& s ) { 

  return cpl::detail_::my_getaddrinfo< SOCK_STREAM >( n.c_str() , s.c_str() ) ;

}

inline datagram_address_list const resolve_datagram
( std::string const& n , std::string const& s ) {

  return cpl::detail_::my_getaddrinfo< SOCK_DGRAM >( n.c_str() , s.c_str() ) ;

}

inline stream_address_list const resolve_stream
( std::string const& s ) {

  return cpl::detail_::my_getaddrinfo< SOCK_STREAM >( 0 , s.c_str() ) ;

}

inline datagram_address_list const resolve_datagram
( std::string const& s ) {
  
  return cpl::detail_::my_getaddrinfo< SOCK_DGRAM >( 0 , s.c_str() ) ;

}


////////////////////////////////////////////////////////////////////////
// Datagram communications.
////////////////////////////////////////////////////////////////////////

struct datagram_receiver {

  typedef datagram_address      address_type      ;
  typedef datagram_address_list address_list_type ;
  typedef unsigned long size_type ;

  static size_type timeout() 
  { return std::numeric_limits< size_type >::max() ; }
  static size_type default_size() { return 65536 ; }

  datagram_receiver( std::string       const& ls ) ;
  datagram_receiver( address_list_type const& la ) ;

  template< typename for_it >
  size_type receive( 
    for_it const& begin , 
    double const& t = -1 ,
    size_type n = default_size()
  ) ;

  address_type const& source() const { return source_ ; }
  address_type const& local () const { return  local_ ; }


private:

  void initialize( address_list_type const& la ) ;

  void reuse_address() ;

  std::auto_ptr< cpl::detail_::datagram_socket > s ;
  
  cpl::detail_::socketfd_t fd() const { assert( s.get() ) ; return s->fd() ; }

  std::vector< char > buffer_ ;

  address_type source_ ;
  address_type  local_ ;

} ;


struct datagram_sender {
  
  typedef datagram_address      address_type      ;
  typedef datagram_address_list address_list_type ;

  typedef unsigned long size_type ;

  datagram_sender() ;
  datagram_sender( std::string const& ls ) ;
  datagram_sender( address_list_type const& la ) ;
  
  // We ignore connection refused error.
  template< typename for_it >
  void send( 
    address_type const& destination ,
    for_it       const& begin       ,
    for_it       const& end
  ) ;
  
  
  template< typename for_it >
  void send( 
    std::string const& node    ,
    std::string const& service ,
    for_it      const& begin   ,
    for_it      const& end
  ) ;


  address_type const& local() const { return  local_ ; } 

private:
  
  std::auto_ptr< cpl::detail_::datagram_socket > s ;
  
  cpl::detail_::socketfd_t fd() const { assert( s.get() ) ; return s->fd() ; }

  address_type local_ ;
  
  std::vector< char > buffer_ ;

  void enable_broadcasting() ;
  
  void initialize( address_list_type const& la ) ;

} ;


////////////////////////////////////////////////////////////////////////
// Stream communications.
////////////////////////////////////////////////////////////////////////


struct connection {
  
  typedef stream_address      address_type      ;
  typedef stream_address_list address_list_type ;

  connection( 
    address_list_type const& ra , 
    address_list_type const& la = address_list_type()
  ) ;

  connection( std::string const& n , std::string const& s ) ;

  connection( acceptor& ) ;

  void no_delay( bool = true ) ;

  address_type const& peer () const { return  peer_ ; }
  address_type const& local() const { return local_ ; }

private:

  friend class cpl::detail_::streambuf_traits< connection > ;

  void shutdown_read () { ::shutdown( fd() , SHUT_RD ) ; }
  void shutdown_write() { ::shutdown( fd() , SHUT_WR ) ; }

  std::auto_ptr< cpl::detail_::stream_socket > s ;

  void initialize(
    address_list_type const& ra , 
    address_list_type const& la
  ) ;

  // Initialize local and peer.
  void initialize_local_peer() ; 

  address_type  peer_ ;
  address_type local_ ;

  cpl::detail_::socketfd_t fd() { assert( s.get() ) ; return s->fd() ; }

} ;


struct acceptor {

  typedef stream_address      address_type      ;
  typedef stream_address_list address_list_type ;

  acceptor( std::string const& ls , int bl = 0 ) ;

  acceptor( address_list_type const& la , int bl = 0 ) ;

  address_type const local() const { return local_ ; }

private:

  void initialize( address_list_type const& la , int bl ) ;

  std::auto_ptr< cpl::detail_::stream_socket > s ;

  cpl::detail_::socketfd_t fd() const { assert( s.get() ) ; return s->fd() ; }

  friend connection::connection( acceptor& ) ;

  stream_address local_ ;

} ;


//
// istreambuf<> and ostreambuf<> specializations for stream (TCP)
// sockets.
//

typedef cpl::util::istreambuf< connection > instreambuf ;
typedef cpl::util::ostreambuf< connection > onstreambuf ;


struct nstreambuf : instreambuf , onstreambuf {

  nstreambuf( connection& c ) : instreambuf( c ) , onstreambuf( c ) {}

} ;


//
// std::istream and std::ostream classes for stream (TCP) sockets.
//

struct instream : std::istream {

  instream( connection& c ) 
  : std::istream( 0 ) , 
    buf         ( c ) 
  { rdbuf( &buf ) ; }

private:

  instreambuf buf ;

} ;


struct onstream : std::ostream {

  onstream( connection& c )
  : std::ostream( 0 ) , 
    buf         ( c ) 
  { rdbuf( &buf ) ; }

private:

  onstreambuf buf ;

} ;

struct nstream : std::iostream {

  nstream( connection& c ) 
  : std::iostream( 0 ) ,
    buf          ( c )
  { rdbuf( &buf ) ; }

private:

  nstreambuf buf ;

} ;


} // namespace network

} // namespace util

} // namespace cpl


////////////////////////////////////////////////////////////////////////
// Template definitions.
////////////////////////////////////////////////////////////////////////

template< int type >
std::vector< cpl::detail_::address< type > >
cpl::detail_::my_getaddrinfo(
  char const* const n ,
  char const* const s
) {

  always_assert( SOCK_DGRAM == type || SOCK_STREAM == type ) ;
  always_assert( n || s ) ;
  int const flags = n ? 0 : AI_PASSIVE ;

  addrinfo* res = 0 ;
  addrinfo  hints   ;

  hints.ai_flags      = flags     ;
  hints.ai_family     = AF_UNSPEC ;
  hints.ai_socktype   = type      ;
  hints.ai_protocol   = 0 ;
  hints.ai_addrlen    = 0 ;
  hints.ai_addr       = 0 ;
  hints.ai_canonname  = 0 ;
  hints.ai_next       = 0 ;

  int const err = getaddrinfo( n , s , &hints , &res ) ;

  if( err ) {

    always_assert( !res ) ;
    
    if( n && s ) {

      throw std::runtime_error( 
          std::string( "can't resolve " )
        + n
        + ":" 
        + s
        + ": " 
        + ::gai_strerror( err ) 
      ) ;

    } else {
      
      throw std::runtime_error( 
          std::string( "can't resolve " )
        + s
        + ": " 
        + ::gai_strerror( err ) 
      ) ;

    }

  }

  std::vector< cpl::detail_::address< type > > ret ;

  for( addrinfo const* p = res ; p ; p = p->ai_next ) { 

    always_assert( type == p->ai_socktype ) ;
    ret.push_back( 
      address< type >( 
        *reinterpret_cast< sockaddr_storage* >( p->ai_addr    ) ,
                                                p->ai_addrlen
        )
      )
    ;

  }

  always_assert( res ) ;
  ::freeaddrinfo( res ) ;

  always_assert( ret.size() >= 1 ) ;

  return ret ;

}


template< int type >
std::string const 
cpl::detail_::my_getnameinfo( 
  cpl::detail_::address< type > const& a ,
  bool             const node    ,
  bool             const numeric ,
  bool             const fqdn
) {

  char n[ NI_MAXHOST ] ;
  char s[ NI_MAXSERV ] ;

  int const flags = 
       ( numeric   ? NI_NUMERICHOST || NI_NUMERICSERV : NI_NAMEREQD )
    || ( fqdn      ? 0                                : NI_NOFQDN   )
    || ( a.dgram() ? NI_DGRAM                         : 0           )
  ;

  int const err =
    node ?
      getnameinfo
      ( a.sockaddr_pointer() , a.length() , n , NI_MAXHOST , 0 , 0 , flags )
    : 
      getnameinfo
      ( a.sockaddr_pointer() , a.length() , 0 , 0 , s , NI_MAXSERV , flags )
  ;

  if( err ) {

    throw std::runtime_error( 
        std::string( "can't get name associated with host: " )
      + gai_strerror( err ) 
    ) ;

  }

  std::string const ret = node ? n : s ;

  if( !node && !numeric ) {

    //
    // Check that we actually have a port name (getnameinfo() doesn't offer a
    // check for this).
    //

    if( ret.empty() ) { return ret ; } // Probably unlikely...

    if( '0' <= ret[ 0 ] && ret[ 0 ] <= 9 ) 
    { throw std::runtime_error( "can't get name associated with port" ) ; }

  }

  return ret ;

}


template< int type >
std::string const
cpl::detail_::address< type >::fqdn() const {

  return cpl::detail_::my_getnameinfo< type >( *this , true , false , true ) ;

}


template< int type >
std::string const
cpl::detail_::address< type >::host_name() const {

  return cpl::detail_::my_getnameinfo< type >( *this , true , false , false ) ;

}

template< int type >
std::string const
cpl::detail_::address< type >::port_name() const {

  return cpl::detail_::my_getnameinfo< type >( *this , false , false , false ) ;

}

template< int type >
std::string const
cpl::detail_::address< type >::host() const {

  return cpl::detail_::my_getnameinfo< type >( *this , true , true , false ) ;

}

template< int type >
std::string const
cpl::detail_::address< type >::port() const {

  return cpl::detail_::my_getnameinfo< type >( *this , false , false , false ) ;

}


template< typename for_it >
cpl::util::network::datagram_receiver::size_type 
cpl::util::network::datagram_receiver::receive( 
  for_it const& begin ,
  double const& t , 
  size_type const max
) {

  if( t >= 0 ) {

    // Watch fd to see if it has input

    fd_set rfds ;
    FD_ZERO(        &rfds ) ;
    FD_SET ( fd() , &rfds ) ;

    // Do not wait.

    ::timeval tv = cpl::detail_::to_timeval( t ) ;

    int nfds = fd() + 1 ;     // Size of array containing sockets 
                              // up to and including the one we set.

    int err ;
    do 
    { err = ::select( nfds , &rfds , 0 , 0 , &tv ) ; } 
    while( cpl::detail_::EINTR_repeat( err ) ) ;  
    // Some platforms including Linux update timeout.

    if( err < 0 ) { cpl::detail_::throw_socket_error( "select" ) ; }

    if( err == 0 ) { return timeout() ; }

  }

  buffer_.resize( max ) ;

  long err ;
  do {

    source_.set_maxlength() ;

    err = 
      ::recvfrom(
         fd()           ,
         &buffer_[ 0 ]  ,
         buffer_.size() ,
         0              ,
         source_.sockaddr_pointer() ,
         source_. socklen_pointer()
      ) ;

  } while( cpl::detail_::EINTR_repeat( err ) ) ;

  if( err < 0 )
  { cpl::detail_::throw_socket_error( "recvfrom" ) ; }

  assert( static_cast< size_type >( err ) <= buffer_.size() ) ;

  std::copy( buffer_.begin() , buffer_.begin() + err , begin ) ;
  return err ;

}


template< typename for_it >
void cpl::util::network::datagram_sender::send(
  address_type const& d ,
  for_it const& begin ,
  for_it const& end
) {

  buffer_.clear() ;
  std::copy( begin , end , std::back_inserter( buffer_ ) ) ;

  long result ;
  
  if( !s.get() ) { 

    cpl::detail_::datagram_socket ds( d.family() ) ;
    result = cpl::detail_::my_sendto
             ( ds.fd() , d , &buffer_[ 0 ] , buffer_.size() ) ;

  } else {

    result = cpl::detail_::my_sendto
             (    fd() , d , &buffer_[ 0 ] , buffer_.size() ) ;

  }

  if( result < 0 ) {

    if( errno == ECONNREFUSED ) { return ; }
    cpl::detail_::throw_socket_error( "send" ) ;

  }

  always_assert( result == static_cast< long >( buffer_.size() ) ) ;

}
  

template< typename for_it >
void cpl::util::network::datagram_sender::send( 
  std::string const& node    , 
  std::string const& service , 
  for_it      const& begin   ,
  for_it      const& end
) {

  address_list_type const& ra = resolve_datagram( node , service ) ;

  if( s.get() ) {

    for( address_list_type::const_iterator i  = ra.begin() ; 
                                           i != ra.end  () ;
                                         ++i ) {

      if( i->family() == local().family() ) {

        send( *i , begin , end ) ; 
        return ;

      }

      throw std::runtime_error( "datagram send: bad address family" ) ;

    }

  } else 
  { send( ra.front() , begin , end ) ; }

}


#endif // CPP_LIB_NETWORK_H
